#include "lisserver.h"
#include "ASTM/ASTM.h"
#include "ASTM/HeaderRecord.h"
#include "ASTM/RequestRecord.h"
#include "ASTM/PatientRecord.h"
#include "ASTM/OrderRecord.h"
#include "ASTM/TerminatorRecord.h"

#include <QDebug>
#include <QLoggingCategory>

Q_LOGGING_CATEGORY(lislog, "LIS")

LISServer::LISServer(const QString &port, QObject *parent)
    : Serial{parent}
{
    ackTimer.setInterval(1000);
    ackTimer.setSingleShot(true);
    connect(&ackTimer, &QTimer::timeout, this, &LISServer::onAckTimeout);

    if(port.isEmpty()) {
        autoOpenLISPort();
    } else {
        connect(serialport, &QSerialPort::errorOccurred, this, &LISServer::onError);
        open(port);
    }

    if(isOpen()) {
        qDebug().noquote() << "serial port" << serialport->portName() << "opened";
        connect(this, &LISServer::frameIn, this, &LISServer::onFrameIn);
        if(port.isEmpty()) {
            connect(serialport, &QSerialPort::errorOccurred, this, &LISServer::onError);
        }
    } else {
        qWarning().noquote() << "fail to open serial port" << port;
    }

    // qDebug(lislog()) << "test";
}

bool LISServer::checkRxData(const QByteArray &frame) const
{
    bool ret;

    switch (frame.size()) {
    case 0:
    case 2:
        ret = false;
        break;

    case 1:
    {
        char c = frame.at(0);

        if(c == ASTM::ENQ || c == ASTM::ACK || c == ASTM::NAK || c == ASTM::EOT) {
            ret = true;
        } else {
            ret = false;
        }
    }
    break;

    default:
    {
        auto CRLF = ASTM::CRLF().toUtf8();

        ret = frame.startsWith(ASTM::STX) && frame.endsWith(CRLF);
    }
    break;
    }

    return ret;
}

bool LISServer::autoOpenLISPort()
{
    bool ret = false;
    auto ports = availablePorts();

    qDebug() << "auto detecting ports:" << ports;
    blockingRecv = true;
    for(auto &p: ports) {
        serialport->setPortName(p);
        if(!serialport->open(QIODevice::ReadWrite)) {
            continue;
        }

        // serialport->clear();
        if(!send(ASTM::ENQ)) {
            serialport->close();
            continue;
        }

        if(!serialport->waitForReadyRead(200)) {
            serialport->close();
            continue;
        }

        char c = 0;
        if(serialport->read(&c, 1) != 1) {
            serialport->close();
            continue;
        }

        if(c == ASTM::ACK) {
            ret = true;
            break;
        }

        serialport->close();
    }

    blockingRecv = false;
    return ret;
}

void LISServer::addRequest(const RequestRecord &request)
{
    QMutexLocker locker(&mutex);

    requests << request;
}


static QString byteToString(char byte)
{
    QString str;

    switch(byte) {
    case ASTM::ENQ:
        str = "<ENQ>";
        break;

    case ASTM::ACK:
        str = "<ACK>";
        break;

    case ASTM::NAK:
        str = "<NAK>";
        break;

    case ASTM::EOT:
        str = "<EOT>";
        break;

    case ASTM::STX:
        str = "<STX>";
        break;

    case ASTM::CR:
        str = "<CR>";
        break;

    case ASTM::LF:
        str = "<LF>";
        break;

    default:
        str = byte;
        break;
    }

    return str;
}


bool LISServer::sendAndDebug(char byte)
{
    bool ret = send(byte);

    if(ret) {
        qDebug(lislog()).noquote() << "TX:" << byteToString(byte);
    }

    return ret;
}

bool LISServer::sendFrame(const QByteArray &data)
{
    QByteArray frame = dataToFrame(data);
    bool ret = Serial::send(frame);
    if(ret) {
        ackTimer.start();
        qDebug(lislog()) << "TX:" << QString(frame);
    }

    return ret;
}

QString LISServer::buildFrame(HeaderRecord::MsgType type, const QString &record) const
{
    HeaderRecord head(type);
    TerminatorRecord terminator;

    QString frame = head.buildRecord();
    frame += record;
    frame += terminator.buildRecord();

    return frame;
}

void LISServer::reply(const RequestRecord &request)
{
    QDateTime now = QDateTime::currentDateTime();
    PatientRecord patient;

    orderIndex += 1;
    QString index = QString::number(orderIndex);
    patient.id = QDateTime::currentDateTime().toString("yyyyMMddHHmmsszzz");
    patient.name = "病人" + index;
    patient.age = QString::number(random.bounded(1, 100));
    patient.sexType = PatientRecord::Male;
    patient.bed = QString::number(random.bounded(1, 100));
    patient.doctor = "医生" + index;
    patient.department = "科室" + index;

    OrderRecord order(request.sampleID, "Serum", now);
    order.projectName = "hs-cTnI";
    patient.addOrder(order);

    order.projectName = "CK-MB";
    patient.addOrder(order);

    QString frame = buildFrame(HeaderRecord::Sample, patient.buildRecord());
    sendFrame(frame.toUtf8());
}

void LISServer::startReply()
{
    QMutexLocker locker(&mutex);
    RequestRecord request = requests.takeFirst();

    reply(request);
}

void LISServer::onRequestIn(const QStringList &records)
{
    if(records.count() < 2) {
        return;
    }

    RequestRecord request;

    request.analyzeRecord(records.at(1));
    addRequest(request);
}

void LISServer::onFrameIn(const QByteArray &frame)
{
    if(frame.size() == 1) {
        char b = frame.at(0);
        qDebug(lislog()).noquote() << "RX:" << byteToString(b);
        switch(b) {
        case ASTM::ENQ:
            if(ackTimer.isActive()) {
                ackTimer.stop();
            } else {
                sendAndDebug(ASTM::ACK);
            }
            break;

        case ASTM::ACK:
            if(ackTimer.isActive()) {
                ackTimer.stop();
                if(!requests.isEmpty()) {
                    startReply();
                } else {
                    sendAndDebug(ASTM::EOT);
                }
            }
            break;

        case ASTM::NAK:
            if(ackTimer.isActive()) {
                ackTimer.stop();
                sendAndDebug(ASTM::ACK);
            }
            break;

        case ASTM::EOT:
            if(!requests.isEmpty()) {
                sendAndDebug(ASTM::ENQ);
                ackTimer.start();
            }
            break;

        default:
            break;
        }
    } else {
        qDebug(lislog()) << "RX:" << QString(frame);
        QString str = dataFromeFrame(frame);
        QStringList records = str.split(ASTM::CR);

        if(records.count() > 0) {
            QString headerStr = records.at(0);
            HeaderRecord header;

            if(header.analyzeRecord(headerStr)) {
                if(header.msgType == HeaderRecord::MsgType::RequestSample) {
                    onRequestIn(records);
                    sendAndDebug(ASTM::ACK);
                } else if(header.msgType == HeaderRecord::MsgType::PatientResult) {
                    // PatientRecord patient;
                    sendAndDebug(ASTM::ACK);
                } else {
                    qWarning() << "LIS: ignore message type:" << header.msgTypeSymbol();
                }
            } else {
                qWarning() << "LIS: unknown header:" << headerStr;
            }
        } else {
            qWarning() << "LIS: unknown message:" << str;
        }
    }
}

void LISServer::onAckTimeout()
{
    QMutexLocker locker(&mutex);

    requests.clear();
    qWarning() << "ACK Timeout!";
}
